/**
 * Copyright (c) 2012 Nokia Corporation. 
 */

package com.nokia.example.tictactoe.connectivity;

/**
 * An abstract class defining the base for all connections.
 */
public abstract class Connection {
	// Constants
	protected static final int MAX_LISTENER_COUNT = 3;
	
	// States
	public static final int NOT_CONNECTED = 0;
	public static final int DISCOVERING = 1;
	public static final int CONNECTING = 2;
	public static final int CONNECTED = 3; 
	public static final int DISCONNECTING = 4;	
	
	// Members
	protected Listener[] _listeners = new Listener[MAX_LISTENER_COUNT];
	protected IConnectionManager _connectionManager = null;
	protected boolean _running = false;
	protected int _state = NOT_CONNECTED;
	
	/** 
	 * @param listener The listener to add for this connection.
	 * @return True if the given listener was added successfully, false otherwise.
	 */
	public boolean addListener(Listener listener) {
		if (listener == null) {
			return false;
		}
		
		for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
			if (_listeners[i] == listener) {
				// The given listener is already added!
				return false;
			}
		}
		
		for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
			if (_listeners[i] == null) {
				_listeners[i] = listener;
				return true;
			}
		}
		
		return false;
	}
	
	/** 
	 * @param listener The listener to remove.
	 * @return True if the given listener was removed successfully, false otherwise.
	 */
	public boolean removeListener(Listener listener) {
		if (listener == null) {
			return false;
		}
		
		for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
			if (_listeners[i] == listener) {
				_listeners[i] = null;
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * For convenience. Clears all listeners including the internal one.
	 */
	public void clearAllListeners() {
		for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
			_listeners[i] = null;
		}
		
		_connectionManager = null;
	}
	
	/**
	 * @param IConnectionManager The internal listener to set.
	 */
	public void setConnectionManager(IConnectionManager connectionManager) {
		_connectionManager = connectionManager;
	}
	
	/** 
	 * @return The connectivity state e.g. Connection.CONNECTING.
	 */
	public final int state() {
		return _state;
	}
	
	/**
	 * Opens the connection.
	 * @param hostname The hostname to use or null if not required.
	 * @param port The port to use.
	 * @return True if successful, false otherwise.
	 */
	abstract public boolean open(String hostname, Integer port);
	
	/**
	 * Sends the given message.
	 * @param message The message to send.
	 * @return True if successful, false otherwise.
	 */
	abstract public boolean send(byte[] message);
	
	/**
	 * Closes the connection.
	 * The default implementation does nothing.
	 */
	public void close() {};
	
	/**
	 * Sets the connectivity state and notifies listeners.
	 * @param state The state to set.
	 */
	protected void setState(final int state) {
		if (_state == state) {
			return;
		}
		
		System.out.println("Connection::setState(): " + state);
		_state = state;
		
		// Check if the connection manager is set. If yes, then let it handle
		// the state change notifications
		if (_connectionManager == null) {
			// No manager set. Go ahead and notify the listeners
			notifyStateChanged(_state);
		}
		else {
			if (_state == CONNECTED) {
				_connectionManager.onConnected(this);
			}
			else if (_state == NOT_CONNECTED) {
				_connectionManager.onDisconnected(this);
			}
		}
	}
	
	// Helper methods for notifying listeners ->
	
	protected void notifyStateChanged(final int state) {
		for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
			if (_listeners[i] != null) {
				_listeners[i].onStateChanged(state);
			}
		}
	}
	
	protected void notifyPeerDisconnected() {
		for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
			if (_listeners[i] != null) {
				_listeners[i].onPeerDisconnected();
			}
		}
	}
	
	protected void notifyMessageSent(final byte[] message) {
		for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
			if (_listeners[i] != null) {
				_listeners[i].onMessageSent(message);
			}
		}
	}
	
	protected void notifyReceived(final byte[] message) {
		for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
			if (_listeners[i] != null) {
				_listeners[i].onReceived(message);
			}
		}
	}
	
	protected void notifyTimeout() {
		if (_connectionManager != null) {
			_connectionManager.onTimeout();
		}
		else {
			for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
				if (_listeners[i] != null) {
					_listeners[i].onTimeout();
				}
			}
		}
	}
	
	protected void notifyError(final String errorMessage) {
		System.out.println("Connection::notifyError(): " + errorMessage);
		
		for (int i = 0; i < MAX_LISTENER_COUNT; ++i) {
			if (_listeners[i] != null) {
				_listeners[i].onError(errorMessage);
			}
		}
	}
	
	protected void notifyFatalError(final String errorMessage) {
		if (_connectionManager != null) {
			System.out.println("Connection::notifyFatalError(): " + errorMessage);
			_connectionManager.onFatalError(this, errorMessage);
		}
		else {		
			notifyError(errorMessage);
		}
	}
	
	protected void log(final String message) {
		if (_connectionManager != null) {
			_connectionManager.onLogRequest(message);
		}
	}
	
	// <- helper methods for notifying listeners
	
	/**
	 * Interface for external connection listeners.
	 */
	public interface Listener {
		void onStateChanged(int state);
		void onPeerDisconnected();
		void onMessageSent(byte[] message);
		void onReceived(byte[] message);
		void onTimeout();
		void onError(String errorMessage);
	}
	
	/**
	 * Interface for internal connection listener. 
	 */
	protected interface IConnectionManager {
		void onConnected(Connection connection);
		void onDisconnected(Connection connection);
		void onTimeout();
		void onFatalError(Connection connection, String errorMessage);
		void onLogRequest(String message); // Mainly for debugging
	}
}
